04 Activity启动模式

ActivityManagerService(AMS)内部维护着一个ActivityStack栈,系统会将Activity实例一一放入栈内(先进后出),默认情况下创建一个实例压入栈内。如果多次创建同一个Activity,栈内将压入多个实例。为此,Android提供了启动模式来修改系统的默认行为。目前有四种启动模式:standard、singleTop、singleTask和singleInstance。

一、代码

BaseActivity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.xianxiaotao.launchmode;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
/**
* Created by xianxiaotao on 17/5/23.
*
* 基类:输出Activity实例信息及所属任务栈信息
*/
public class BaseActivity extends AppCompatActivity {
private static final String TAG = "Xian Xiaotao";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 区别类名,区别同类名是否同实例,区别任务栈
Log.i(TAG, "onCreate:" + getClass().getSimpleName() + " hasCode:" + this.hashCode() + " TaskId: " + getTaskId());
// 任务栈名称,默认为包名相关
dumpTaskAffinity();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.i(TAG, "onNewIntent:" + getClass().getSimpleName() + " hasCode:" + this.hashCode() + " TaskId: " + getTaskId());
dumpTaskAffinity();
}
protected void dumpTaskAffinity(){
try {
ActivityInfo info = this.getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
Log.i(TAG, "taskAffinity:" + info.taskAffinity);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}

MainActivity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.xianxiaotao.launchmode;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.startMainButton).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, MainActivity.class));
}
});
findViewById(R.id.startOtherButton).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, OtherActivity.class));
}
});
}
}

OtherActivity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.xianxiaotao.launchmode;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
public class OtherActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);
findViewById(R.id.startMainButton).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(OtherActivity.this, MainActivity.class));
}
});
findViewById(R.id.startOtherButton).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(OtherActivity.this, OtherActivity.class));
}
});
}
}

布局文件activity_main.xml,activity_other.xml文件与此类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is main activity"/>
<Button
android:id="@+id/startMainButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="Start Main Activity"/>
<Button
android:id="@+id/startOtherButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="Start Other Activity"/>
</LinearLayout>

二、测试

1、standard

两个Activity都没有设置启动模式,即默认为标准模式。然后启动App,点击Start Main Activity按钮,然后再点击Start Other Activity按钮,然后再点击Start Main Activity。日志输出如下:

1
2
3
4
5
6
7
8
I/Xian Xiaotao: onCreate:MainActivity hasCode:110059330 TaskId: 523
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
I/Xian Xiaotao: onCreate:MainActivity hasCode:250944109 TaskId: 523
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
I/Xian Xiaotao: onCreate:OtherActivity hasCode:39080781 TaskId: 523
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
I/Xian Xiaotao: onCreate:MainActivity hasCode:42988356 TaskId: 523
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode

上述日志中所有的hashCode都不相同,即每次启动Activity都创建一个新的实例。连续按四次返回键退出应用。
TaskId都相同,表明这些实例都在同一个任务栈里。

2、singleTop

在AndroidMainfest.xml文件中,为MainActivity添加属性:android:launchMode=”singleTop”。启动App、点击Start Main Activity按钮、再点击Start Other Activity按钮,再点击Start Main Activity。日志输出如下:

1
2
3
4
5
6
7
8
I/Xian Xiaotao: onCreate:MainActivity hasCode:29797800 TaskId: 525
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
I/Xian Xiaotao: onNewIntent:MainActivity hasCode:29797800 TaskId: 525
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
I/Xian Xiaotao: onCreate:OtherActivity hasCode:42572600 TaskId: 525
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
I/Xian Xiaotao: onCreate:MainActivity hasCode:97104763 TaskId: 525
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode

第一次点击Start Main Activity按钮时,调用了onNewIntent方法,且hashCode与之前的MainActivity实例的hashCode相同,即复用实例而不是重新创建。第二次点击Start Main Activity按钮时,也就是说从OtherActivity里启动MainActivity,此时调用了onCreate方法,而且hashCode与之前的不相同,即重新创建了MainActivity的实例。连续按三次返回键退出应用。
TaskId都相同,表明这些实例都在同一个任务栈里。

3、singleTask

将MainActivity设置为singleTop,然后执行之前相同的动作。日志输出如下:

1
2
3
4
5
6
7
8
I/Xian Xiaotao: onCreate:MainActivity hasCode:29797800 TaskId: 527
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
I/Xian Xiaotao: onNewIntent:MainActivity hasCode:29797800 TaskId: 527
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
I/Xian Xiaotao: onCreate:OtherActivity hasCode:121783201 TaskId: 527
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
I/Xian Xiaotao: onNewIntent:MainActivity hasCode:29797800 TaskId: 527
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode

此时发现MainActivity的实例都相同,即第一次创建后,后面的都是复用实例而不是重新创建。按一次返回键退出应用。
TaskId都相同,表明这些实例都在同一个任务栈里。

4、singleInstance

将MainActivity设置为singleInstance,然后执行之前相同的动作。日志输出如下:
I/Xian Xiaotao: onCreate:MainActivity hasCode:29797800 TaskId: 529
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
I/Xian Xiaotao: onNewIntent:MainActivity hasCode:29797800 TaskId: 529
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
I/Xian Xiaotao: onCreate:OtherActivity hasCode:42572600 TaskId: 530
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
I/Xian Xiaotao: onNewIntent:MainActivity hasCode:29797800 TaskId: 529
I/Xian Xiaotao: taskAffinity:com.xianxiaotao.launchmode
此时发现MainActivity的实例依然相同,即第一次创建后,后面的都是复用实例而不是重新创建。按两次返回键退出应用。
TaskId不同,即MainActivity与OtherActivity不在一个Activity中。

读者可以通过多个Activity设置不同的启动模式进行复杂的测试。

三、结论

standard

标准模式,也是系统默认模式。每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。ABCBB

singleTop

栈顶复用模式。如果新Activity已经位于任务栈的栈顶,那么只调用它的onNewIntent方法实现复用,而不会创建新的实例。如果新的Activity的实例已存在但不是位于栈顶,仍然重新创建。假设目前栈内情况为ABCD(ABCD为四个Activity,A位于栈底),此时再次启动,如果D的启动模式为singleTop,那么栈内仍然是ABCD;如果D的启动模式为standard,D将被重新创建,栈内情况变为ABCDD。

singleTask

栈内复用模式。在同一个栈内,它是单实例模式。在这种模式下,启动一个Activity A,系统首先会寻找是否存在A想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例并压入栈内;如果存在所需的任务栈,这时要看A是否在栈中有实例存在,如果有,那么系统就会把A调到栈顶并调用它的onNewIntent方法,不存在就创建并压入栈中。
1、目前任务栈栈S1(ABC),此时Activity D以singleTask模式请求启动,其所需的任务栈为S2,由于S2和D的实例均不存在,所以系统先创建任务栈S2,然后再创建D的实例并将其放入栈S2中。
2、另外一种情况,假设D所需的任务栈为S1,上述S1已存在,所以系统会直接创建D的实例并压入栈S1内。
3、如果D所需的任务栈为S1,其中为ADBC,此时系统将D上面的Activity出栈,把D切换到栈顶并调用onNewIntent方法,最终S1栈内为AD。

singleInstance:

该模式具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。

启动模式参考博客